Concepte fundamentale de limbaje de programare

6. Fundamentele. Structura programului. Sistem Type

6.1 Structura generală a unui program C#

Programele C# constau din unul sau mai multe fișiere. Fiecare fișier conține zero sau mai multe namespace-uri. Un namespace conține tipuri precum clase, structuri, interfețe, enumerări și delegați sau alte namespace-uri. Următorul exemplu este scheletul unui program C# care conține toate aceste elemente.

// Un schelet al unui program C#
using System;

// Your program starts here:
Console.WriteLine("Hello world!");

namespace YourNamespace
{
    class YourClass
    {
    }

    struct YourStruct
    {
    }

    interface IYourInterface
    {
    }

    delegate int YourDelegate();

    enum YourEnum
    {
    }

    namespace YourNestedNamespace
    {
        struct YourStruct
        {
        }
    }
}

Exemplul precedent utilizează instrucțiuni de nivel superior pentru punctul de intrare al programului. Această caracteristică a fost adăugată în C# 9. Înainte de C# 9, punctul de intrare era o metodă statică numită Main, așa cum se arată în exemplul următor:

// A skeleton of a C# program
using System;
namespace YourNamespace
{
    class YourClass
    {
    }

    struct YourStruct
    {
    }

    interface IYourInterface
    {
    }

    delegate int YourDelegate();

    enum YourEnum
    {
    }

    namespace YourNestedNamespace
    {
        struct YourStruct
        {
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            //Your program starts here...
            Console.WriteLine("Hello world!");
        }
    }
}

6.2 Main() și argumente din linia de comandă

Metoda Main este punctul de intrare al unei aplicații C#. (Bibliotecile și serviciile nu necesită o metodă Main ca punct de intrare.) Când aplicația este pornită, metoda Main este prima metodă care este invocată.

Nu poate exista decât un singur punct de intrare într-un program C#. Dacă aveți mai mult de o clasă care are o metodă Main, trebuie să compilați programul cu opțiunea de compilare StartupObject pentru a specifica ce metodă Main să utilizați ca punct de intrare. Pentru mai multe informații, consultați StartupObject (C# Compiler Options).

class TestClass
{
    static void Main(string[] args)
    {
        // Display the number of command line arguments.
        Console.WriteLine(args.Length);
    }
}

Începând cu C# 9, puteți omite metoda Main și puteți scrie instrucțiuni C# ca și cum ar fi în metoda Main, ca în exemplul următor:

using System.Text;

StringBuilder builder = new();
builder.AppendLine("Hello");
builder.AppendLine("World!");

Console.WriteLine(builder.ToString());

6.3 Prezentare generală asupra metodei Main

Metoda Main este punctul de intrare al unui program executabil; este locul unde începe și se termină controlul programului. Main este declarată în interiorul unei clase sau structuri. Main trebuie să fie static și nu trebuie să fie public. (În exemplul anterior, primește accesul implicit de privat.) Clasa sau structura care înglobează nu este necesar să fie statică. Main poate avea fie un tip de returnare void, int, sau, începând cu C# 7.1, Task sau Task. Dacă și numai dacă Main returnează un Task sau Task, declarația Main poate include modificatorul async. Acest lucru exclude în mod specific o metodă asincronă Main de tip void.

Metoda Main poate fi declarată cu sau fără un parametru string[] care conține argumente de linie de comandă. Când utilizați Visual Studio pentru a crea aplicații Windows, puteți adăuga manual parametrul sau puteți utiliza metoda GetCommandLineArgs() pentru a obține argumentele liniei de comandă. Parametrii sunt citiți ca argumente de linie de comandă indexate la zero. Spre deosebire de C și C++, numele programului nu este tratat ca primul argument de linie de comandă din tabloul args, dar este primul element al metodei GetCommandLineArgs().

Următoarea listă arată semnăturile valide de la Main:

public static void Main() { }
public static int Main() { }
public static void Main(string[] args) { }
public static int Main(string[] args) { }
public static async Task Main() { }
public static async Task<int> Main() { }
public static async Task Main(string[] args) { }
public static async Task<int> Main(string[] args) { }

In toate exemplele precedente se folosește modificatorul de acces public. Este folosit deseori, dar nu este obligatoriu.

Adăugarea tipurilor de returnare async și Task, Task<int> simplifică codul programului atunci când aplicațiile de consolă trebuie să pornească și să aștepte operațiuni asincrone în Main.

6.4 Valorile returnate de Main()

Puteți returna un int din metoda Main definind metoda într-unul dintre următoarele moduri:

Metoda codului Main Semnătura Main
Fără a folosi args sau await static int Main()
Folosim args, nu folosim await static int Main(string[] args)
Nu folosim args, folosim await static async Task Main()
Folosim args și await static async Task Main(string[] args)

Dacă valoarea returnată de la Main nu este utilizată, returnarea void sau Task permite un cod puțin mai simplu.

Metoda codului Main Semnătura Main
Fără a folosi args sau await static void Main()
Folosim args, nu folosim await static void Main(string[] args)
Nu folosim args, folosim await static async Task Main()
Folosim args și await static async Task Main(string[] args)

Cu toate acestea, returnarea int sau Task<int> permite programului să comunice informații de stare altor programe sau scripturi care invocă fișierul executabil.

Următorul exemplu arată cum poate fi accesat codul de ieșire pentru proces.

Acest exemplu utilizează instrumente de linie de comandă .NET Core. Dacă nu sunteți familiarizat cu instrumentele de linie de comandă .NET Core, puteți afla despre ele în acest articol introductiv.

Creați o nouă aplicație rulând dotnet new console. Modificați metoda Main în Program.cs după cum urmează:

// Salvați acest program ca MainReturnValTest.cs.
class MainReturnValTest
{
    static int Main()
    {
        //...
        return 0;
    }
}

Când un program este executat în Windows, orice valoare returnată de la funcția Main este stocată într-o variabilă de mediu. Această variabilă de mediu poate fi preluată folosind ERRORLEVEL dintr-un fișier batch sau $LastExitCode din PowerShell.

Puteți crea aplicația folosind comanda dotnet CLI dotnet build.

Apoi, creați un script PowerShell pentru a rula aplicația și pentru a afișa rezultatul. Copiați următorul cod într-un fișier text și salvați-l ca test.ps1 în folderul care conține proiectul. Rulați scriptul PowerShell tastând test.ps1 la promptul PowerShell.

Deoarece codul returnează zero, fișierul batch va raporta succes. Cu toate acestea, dacă modificați MainReturnValTest.cs pentru a returna o valoare diferită de zero și apoi recompilați programul, execuția ulterioară a scriptului PowerShell va raporta o eroare.

dotnet run
if ($LastExitCode -eq 0) {
    Write-Host "Execution succeeded"
} else
{
    Write-Host "Execution Failed"
}
Write-Host "Return value = " $LastExitCode
Output
Execution succeeded
Return value = 0

6.5 Valori de returnare asincrone Main

Când declarați o valoare de returnare async pentru Main, compilatorul generează codul standard pentru apelarea metodelor asincrone în Main. Dacă nu specificați cuvântul cheie async, trebuie să scrieți singur codul respectiv, așa cum se arată în exemplul următor. Codul din exemplu asigură că programul dumneavoastră rulează până la finalizarea operației asincrone:

public static void Main()
{
    AsyncConsoleWork().GetAwaiter().GetResult();
}

private static async Task AsyncConsoleWork()
{
    // Main body here
    return 0;
}

Acest cod boilerplate poate fi înlocuit cu:

static async Task Main(string[] args)
{
    return await AsyncConsoleWork();
}

Un avantaj al declarării Main ca async este că compilatorul generează întotdeauna codul corect.

Când punctul de intrare al aplicației returnează un Task sau Task, compilatorul generează un nou punct de intrare care apelează metoda punctului de intrare declarată în codul aplicației. Presupunând că acest punct de intrare se numește $GeneratedMain, compilatorul generează următorul cod pentru aceste puncte de intrare:

Notă Dacă exemplele au folosit modificatorul async pe metoda Main, compilatorul va genera același cod.

6.6 Argumente din linia de comandă

Puteți trimite argumente la metoda Main definind metoda într-unul din următoarele moduri:

Metoda codului Main Semnătura Main
Fără valoare returnată, nu folosim await static void Main(string[] args)
Valoare returnată, nu folosim await static int Main(string[] args)
Fără valoare returnată, folosim await static async Task Main(string[] args)
Valoare returnată, folosim await static async Task Main(string[] args)

Dacă argumentele nu sunt folosite, puteți omite argumentele din semnătura metodei pentru un cod puțin mai simplu:

Metoda codului Main Semnătura Main
Fără valoare returnată, nu folosim await static void Main()
Valoare returnată, nu folosim await static int Main()
Valoare returnată, nu folosim await static async Task Main()
Valoare returnată, folosim await static async Task Main()

Notă De asemenea, puteți utiliza Environment.CommandLine sau Environment.GetCommandLineArgs pentru a accesa argumentele liniei de comandă din orice punct dintr-o consolă sau aplicație Windows Forms. Pentru a activa argumentele liniei de comandă în semnătura metodei Main într-o aplicație Windows Forms, trebuie să modificați manual semnătura Main. Codul generat de designerul Windows Forms creează Main fără un parametru de intrare.

Parametrul metodei Main este o matrice String care reprezintă argumentele liniei de comandă. De obicei, determinați dacă există argumente testând proprietatea Length, de exemplu:

if (args.Length == 0)
{
    System.Console.WriteLine("Please enter a numeric argument.");
    return 1;
}

Tip Matricea args nu poate fi nulă. Deci, este sigur să accesați proprietatea Length fără verificarea de nul.

De asemenea, puteți converti argumentele șir în tipuri numerice utilizând clasa Convert sau metoda Parse. De exemplu, următoarea instrucțiune convertește șirul într-un număr de tip long folosind metoda Parse:

long num = Int64.Parse(args[0]);

De asemenea, este posibil să utilizați tipul C# long, care numește Int64:

long num = long.Parse(args[0]);

De asemenea, puteți utiliza metoda Convert class ToInt64 pentru a face același lucru:

long num = Convert.ToInt64(s);

Pentru mai multe informații, consultați Parse and Convert.

Următorul exemplu arată cum să utilizați argumentele liniei de comandă într-o aplicație consolă. Aplicația ia un argument în timpul rulării, îl convertește într-un număr întreg și calculează factorialul numărului. Dacă nu sunt furnizate argumente, aplicația emite un mesaj care explică utilizarea corectă a programului.

Pentru a compila și a rula aplicația dintr-un prompt de comandă, urmați acești pași:

Copiați următorul cod în orice editor de text și apoi salvați fișierul ca fișier text cu numele Factorial.cs.

public class Functions
{
    public static long Factorial(int n)
    {
        // Testam pentru input invalid.
        if ((n < 0) || (n > 20))
        {
            return -1;
        }

        // Calculați factorialul iterativ, mai degrabă decât recursiv.
        long tempResult = 1;
        for (int i = 1; i <= n; i++)
        {
            tempResult *= i;
        }
        return tempResult;
    }
}

class MainClass
{
    static int Main(string[] args)
    {
        // Testam daca argumentele de intrare au fost furnizate
        if (args.Length == 0)
        {
            Console.WriteLine("Please enter a numeric argument.");
            Console.WriteLine("Usage: Factorial <num>");
            return 1;
        }

        // Încercați să convertiți argumentele de intrare în numere. Aceasta va arunca
        // o excepție dacă argumentul nu este un număr.
        // num = int.Parse(args[0]);
        int num;
        bool test = int.TryParse(args[0], out num);
        if (!test)
        {
            Console.WriteLine("Please enter a numeric argument.");
            Console.WriteLine("Usage: Factorial ");
            return 1;
        }

        // Calculam factorialul.
        long result = Functions.Factorial(num);

        // Print result.
        if (result == -1)
            Console.WriteLine("Input must be >= 0 and <= 20.");
        else
            Console.WriteLine($"The Factorial of {num} is {result}.");

        return 0;
    }
}
// Dacă 3 este introdus pe linia de comandă,
// ieșirea arată: factorialul 3 este 6.

Din ecranul Start sau meniul Start, deschideți o fereastră Visual Studio Developer Command Prompt, apoi navigați la folderul care conține fișierul pe care l-ați creat.

Introduceți următoarea comandă pentru a compila aplicația.

dotnet build

Dacă aplicația dumneavoastră nu are erori de compilare, se va crea un fișier executabil numit Factorial.exe.

Introduceți următoarea comandă pentru a calcula factorialul de 3:

dotnet run -- 3
Comanda produce această ieșire: The factorial of 3 is 6.

Notă Când rulați o aplicație în Visual Studio, puteți specifica argumente de linie de comandă în Debug Page, Project Designer.

6.7 Declarații de nivel superior - programe fără metode Main

Începând cu C# 9, nu trebuie să includeți în mod explicit o metodă Main într-un proiect de aplicație consolă. În schimb, puteți utiliza funcția de instrucțiuni de nivel superior pentru a minimiza codul pe care trebuie să îl scrieți. În acest caz, compilatorul generează o clasă și un punct de intrare pentru metoda Main di naplicație.

Iată un fișier Program.cs care este un program C# complet în C# 10:

Console.WriteLine("Hello World!");

Declarațiile de nivel superior vă permit să scrieți programe simple pentru utilitare mici, cum ar fi Azure Functions și GitHub Actions. De asemenea, facilitează pentru noii programatori C# să înceapă să învețe și să scrie cod.

Următoarele secțiuni explică regulile cu privire la ceea ce poți și ce nu poți face cu declarațiile de nivel superior.

6.8 Un singur fișier de nivel superior

O aplicație trebuie să aibă un singur punct de intrare. Un proiect poate avea un singur fișier cu instrucțiuni de nivel superior. Introducerea instrucțiunilor de nivel superior în mai mult de un fișier dintr-un proiect are ca rezultat următoarea eroare a compilatorului:

CS8802 Only one compilation unit can have top-level statements.

Un proiect poate avea orice număr de fișiere suplimentare de cod sursă care nu au instrucțiuni de nivel superior.

6.9 Fără alte puncte de intrare

Puteți scrie o metodă Main în mod explicit, dar nu poate funcționa ca punct de intrare. Compilatorul emite următorul avertisment:
CS7022 The entry point of the program is global code; ignoring 'Main()' entry point.

Într-un proiect cu instrucțiuni de nivel superior, nu puteți utiliza opțiunea -main compilator pentru a selecta punctul de intrare, chiar dacă proiectul are una sau mai multe metode Main.

6.10 Directivele "using".

Dacă includeți directive using, acestea trebuie să apară pe primul loc în fișier, ca în acest exemplu:

using System.Text;

StringBuilder builder = new();
builder.AppendLine("Hello");
builder.AppendLine("World!");

Console.WriteLine(builder.ToString());

6.11 Namespace global

Declarațiile de nivel superior sunt implicit în namespace-ul global.

6.12 Namespace și definiții type

Un fișier cu instrucțiuni de nivel superior poate conține, de asemenea, namespace-uri și definiții type, dar acestea trebuie să vină după instrucțiunile de nivel superior. De exemplu:
MyClass.TestMethod();
MyNamespace.MyClass.MyMethod();

public class MyClass
{
    public static void TestMethod()
    {
        Console.WriteLine("Hello World!");
    }

}

namespace MyNamespace
{
    class MyClass
    {
        public static void MyMethod()
        {
            Console.WriteLine("Hello World from MyNamespace.MyClass.MyMethod!");
        }
    }
}

6.13 args

Instrucțiunile de nivel superior pot face referire la variabila args pentru a accesa orice argument din linia de comandă care au fost introduse. Variabila args nu este niciodată nulă, dar lungimea sa este zero dacă nu au fost furnizate argumente de linie de comandă. De exemplu:

if (args.Length > 0)
{
    foreach (var arg in args)
    {
        Console.WriteLine($"Argument={arg}");
    }
}
else
{
    Console.WriteLine("No arguments");
}

6.14 await

Puteți apela o metodă asincronă utilizând await. De exemplu:

Console.Write("Hello ");
await Task.Delay(5000);
Console.WriteLine("World!");

6.14 Cod de ieșire pentru proces

Pentru a returna o valoare int când aplicația se termină, utilizați instrucțiunea return așa cum ați face într-o metodă Main care returnează un int. De exemplu:

string? s = Console.ReadLine();

int returnValue = int.Parse(s ?? "-1");
return returnValue;

6.15 Metoda punctului de intrare implicit

Compilatorul generează o metodă care să servească drept punct de intrare în program pentru un proiect cu instrucțiuni de nivel superior. Numele acestei metode nu este de fapt Main, este un detaliu de implementare la care codul dumneavoastră nu poate face referire direct. Semnătura metodei depinde dacă instrucțiunile de nivel superior conțin cuvântul cheie await sau instrucțiunea return. Următorul tabel arată cum ar arăta semnătura metodei, folosind numele de metodă Main din tabel pentru comoditate.

Codul de nivel superior conține Semnătura Main implicită
await și return static async Task Main(string[] args)
await static async Task Main(string[] args)
return static int Main(string[] args)
Fără await sau return static void Main(string[] args)

6.16 Sistemul de tip C#

6.17 Namespaces

6.18 Clase

6.19 Records

6.20 Interfețe

6.21 Generice

6.22 Tipuri anonime

6.23 Sarcini

TODO

Bibliografie

[1] Microsoft Corporation. C# Documentation, https://docs.microsoft.com/en-us/dotnet/csharp/, 2022.